﻿using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Sidvall.Reflection
{
    public class CodeManager
    {
        #region Public Members

        public Sidvall.Reflection.ICode Code { get; set; }
        #region StorageManager

        private Sidvall.IO.StorageManager _StorageManager;
        public Sidvall.IO.StorageManager StorageManager
        {
            get
            {
                if (_StorageManager != null)
                    return _StorageManager;
                return Sidvall.SystemContext.Current.SystemStorageManager;
            }
            set
            {
                _StorageManager = value;
            }
        }

        #endregion

        #region CopyFieldValues

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CopyFieldValues(object source, object destination, params string[] fieldNames)
        {
            CopyFieldValues(source, destination, fieldNames.AsEnumerable());
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CopyFieldValues(object source, object destination, IEnumerable<string> fieldNames)
        {
            System.Reflection.FieldInfo sourceFieldInfo, destinationFieldInfo;
            object value;

            if ((source == null) || (destination == null) || (fieldNames == null))
                return;
            foreach (var fieldName in fieldNames)
            {
                sourceFieldInfo = GetField(source, fieldName);
                if (sourceFieldInfo == null)
                    continue;
                destinationFieldInfo = GetField(destination, fieldName);
                if (destinationFieldInfo == null)
                    continue;
                value = sourceFieldInfo.GetValue(source);
                destinationFieldInfo.SetValue(destination, value);
            }
        }

        #endregion
        #region CopyPropertyValues

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CopyPropertyValues(object source, object destination, params string[] propertyNames)
        {
            CopyPropertyValues(source, destination, propertyNames.AsEnumerable());
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void CopyPropertyValues(object source, object destination, IEnumerable<string> propertyNames)
        {
            System.Reflection.PropertyInfo sourcePropertyInfo, destinationPropertyInfo;
            object value;

            if ((source == null) || (destination == null) || (propertyNames == null))
                return;
            foreach (var propertyName in propertyNames)
            {
                sourcePropertyInfo = GetProperty(source, propertyName);
                if (sourcePropertyInfo == null)
                    continue;
                destinationPropertyInfo = GetProperty(destination, propertyName);
                if (destinationPropertyInfo == null)
                    continue;
                value = sourcePropertyInfo.GetValue(source);
                destinationPropertyInfo.SetValue(destination, value);
            }
        }

        #endregion
        #region CreateInstance

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public object CreateInstance(System.Type type)
        {
            if (type == null)
                return null;
            return System.Activator.CreateInstance(type);
        }
        public object CreateInstance(string typeName)
        {
            var type = GetType(typeName);
            return CreateInstance(type);
        }
        public object CreateInstance(string typeName, Assembly assembly)
        {
            var type = GetType(typeName, assembly);
            return CreateInstance(type);
        }
        public object CreateInstance(string typeName, IEnumerable<Assembly> assemblies)
        {
            var type = GetType(typeName, assemblies);
            return CreateInstance(type);
        }

        #endregion
        #region CreateInstanceAsync

        public async Task<T> CreateInstanceAsync<T>(InstanceItem item)
        {
            var factory = Sidvall.SystemContext.Current.CodeManager.CreateInstance(item.FactoryTypeName) as IInstanceItemFactory<T>;
            if (factory != null)
                return await factory.CreateAsync(item.Context).ConfigureAwait(false);
            return default(T);
        }

        #endregion
        #region CreateInstancesAsync

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        public async Task<IEnumerable<T>> CreateInstancesAsync<T>(InstanceItemCollection items)
        {
            var values = new List<T>();
            if (items != null)
            {
                foreach (var item in items)
                {
                    var value = await CreateInstanceAsync<T>(item).ConfigureAwait(false);
                    if (value != null)
                        values.Add(value);
                }
            }
            return values;
        }

        #endregion
        #region GetAssemblyAsync

        public async Task<Assembly> GetAssemblyAsync(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                return null;
            if (!await this.StorageManager.FileExistsAsync(path).ConfigureAwait(false))
                return null;
            var file = await this.StorageManager.ReadAllBytesAsync(path).ConfigureAwait(false);
            return await this.Code.GetAssemblyAsync(file).ConfigureAwait(false);
        }

        #endregion
        #region GetAssemblyTypeNames

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public string[] GetAssemblyTypeNames(Assembly assembly, bool? isClass, bool? isAbstract, bool? isPublic, TypeInfo baseType)
        {
            return this.Code.GetAssemblyTypeNames(assembly, isClass, isAbstract, isPublic, baseType);
        }

        #endregion
        #region GetAssembliesAsync

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public async Task<IEnumerable<Assembly>> GetAssembliesAsync()
        {
            return await this.Code.GetAssembliesAsync().ConfigureAwait(false);
        }

        #endregion
        #region GetAttributeValue

        public object GetAttributeValue(System.Attribute item, string propertyName)
        {
            if (item == null)
                return null;
            return GetPropertyValue(item, propertyName);
        }
        public object GetAttributeValue(MemberInfo item, System.Type attributeType, string propertyName)
        {
            if (item == null)
                return null;
            var attribute = item.GetCustomAttribute(attributeType, false);
            return GetPropertyValue(attribute, propertyName);
        }

        #endregion
        #region GetField

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.FieldInfo GetField(object item, string fieldName)
        {
            if (item == null)
                return null;
            return GetField(item.GetType(), fieldName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.FieldInfo GetField(System.Type type, string fieldName)
        {
            if ((type == null) || (string.IsNullOrWhiteSpace(fieldName)))
                return null;
            return type.GetTypeInfo().GetDeclaredField(fieldName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.FieldInfo GetField(IEnumerable<FieldInfo> properties, string fieldName)
        {
            if ((properties == null) || (string.IsNullOrWhiteSpace(fieldName)))
                return null;
            return (from o in properties
                    where o.Name == fieldName
                    select o).FirstOrDefault();
        }
        public System.Reflection.FieldInfo GetField(IEnumerable<FieldInfo> properties, System.Type attributeType, string attributeFieldName, string value)
        {
            if ((properties == null) || (attributeType == null) || (string.IsNullOrWhiteSpace(attributeFieldName)))
                return null;
            foreach (var item in properties)
            {
                var attributeValue = GetAttributeValue(item, attributeType, attributeFieldName);
                if (attributeValue == null)
                    continue;
                if (attributeValue.ToString() == value)
                    return item;
            }
            return null;
        }

        #endregion
        #region GetFieldValue

        public object GetFieldValue(object item, string fieldName)
        {
            var fieldInfo = GetField(item, fieldName);
            return GetFieldValue(item, fieldInfo);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public object GetFieldValue(object item, System.Reflection.FieldInfo fieldInfo)
        {
            if (fieldInfo == null)
                return null;
            return fieldInfo.GetValue(item);
        }

        #endregion
        #region GetFields

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public IEnumerable<FieldInfo> GetFields(System.Type type, bool? isInterface, bool? isAbstract, bool? isPublic, string name)
        {
            return this.Code.GetFields(type, isInterface, isAbstract, isPublic, name);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public IEnumerable<FieldInfo> GetFields(System.Type type, System.Type attributeType)
        {
            var items = new List<FieldInfo>();
            var allItems = type.GetTypeInfo().DeclaredFields;
            foreach (var item in allItems)
            {
                var attribute = item.GetCustomAttribute(attributeType, false);
                if (attribute == null)
                    continue;
                items.Add(item);
            }
            return items;
        }

        #endregion
        #region GetProperties

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public IEnumerable<PropertyInfo> GetProperties(System.Type type, bool? isInterface, bool? isAbstract, bool? isPublic, string name)
        {
            return this.Code.GetProperties(type, isInterface, isAbstract, isPublic, name);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public IEnumerable<PropertyInfo> GetProperties(System.Type type, System.Type attributeType)
        {
            var items = new List<PropertyInfo>();
            var allItems = type.GetTypeInfo().DeclaredProperties;
            foreach (var item in allItems)
            {
                var attribute = item.GetCustomAttribute(attributeType, false);
                if (attribute == null)
                    continue;
                items.Add(item);
            }
            return items;
        }

        #endregion
        #region GetProperty

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.PropertyInfo GetProperty(object item, string propertyName)
        {
            if (item == null)
                return null;
            return GetProperty(item.GetType(), propertyName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.PropertyInfo GetProperty(System.Type type, string propertyName)
        {
            if ((type == null) || (string.IsNullOrWhiteSpace(propertyName)))
                return null;
            return type.GetTypeInfo().GetDeclaredProperty(propertyName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Reflection.PropertyInfo GetProperty(IEnumerable<PropertyInfo> properties, string propertyName)
        {
            if ((properties == null) || (string.IsNullOrWhiteSpace(propertyName)))
                return null;
            return (from o in properties
                    where o.Name == propertyName
                    select o).FirstOrDefault();
        }
        public System.Reflection.PropertyInfo GetProperty(IEnumerable<PropertyInfo> properties, System.Type attributeType, string attributePropertyName, string value)
        {
            if ((properties == null) || (attributeType == null) || (string.IsNullOrWhiteSpace(attributePropertyName)))
                return null;
            foreach (var item in properties)
            {
                var attributeValue = GetAttributeValue(item, attributeType, attributePropertyName);
                if (attributeValue == null)
                    continue;
                if (attributeValue.ToString() == value)
                    return item;
            }
            return null;
        }

        #endregion
        #region GetPropertyValue

        public object GetPropertyValue(object item, string propertyName)
        {
            var propertyInfo = GetProperty(item, propertyName);
            return GetPropertyValue(item, propertyInfo);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public object GetPropertyValue(object item, System.Reflection.PropertyInfo propertyInfo)
        {
            if (propertyInfo == null)
                return null;
            return propertyInfo.GetValue(item);
        }

        #endregion
        #region GetType

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Type GetType(string typeName)
        {
            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            return System.Type.GetType(typeName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Type GetType(string typeName, Assembly assembly)
        {
            Sidvall.Reflection.TypeItem typeItem;

            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            if (assembly != null)
            {
                typeItem = Sidvall.Reflection.TypeItem.Parse(typeName);
                if (TypeIsValid(typeItem, assembly))
                    return assembly.GetType(typeItem.TypeName);
            }
            return System.Type.GetType(typeName);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public System.Type GetType(string typeName, IEnumerable<Assembly> assemblies)
        {
            System.Type type;
            Sidvall.Reflection.TypeItem typeItem;

            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            if (assemblies != null)
            {
                typeItem = Sidvall.Reflection.TypeItem.Parse(typeName);
                foreach (var assembly in assemblies)
                {
                    if (!TypeIsValid(typeItem, assembly))
                        continue;
                    type = assembly.GetType(typeItem.TypeName);
                    if (type != null)
                        return type;
                }
            }
            return System.Type.GetType(typeName);
        }

        #endregion
        #region GetTypeAsync

        public async Task<System.Type> GetTypeAsync(string typeName, string assemblyFilePath)
        {
            Assembly assembly;

            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            assembly = await GetAssemblyAsync(assemblyFilePath).ConfigureAwait(false);

            return GetType(typeName, assembly);
        }
        public async Task<System.Type> GetTypeAsync(string typeName, IEnumerable<string> assemblyFilePaths)
        {
            System.Type type;
            Assembly assembly;

            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            if (assemblyFilePaths != null)
            {
                foreach (var assemblyFilePath in assemblyFilePaths)
                {
                    assembly = await GetAssemblyAsync(assemblyFilePath).ConfigureAwait(false);
                    if (assembly != null)
                    {
                        type = assembly.GetType(typeName);
                        if (type != null)
                            return type;
                    }
                }
            }
            return System.Type.GetType(typeName);
        }

        #endregion
        #region GetTypeFromFolderAsync

        public async Task<System.Type> GetTypeFromFolderAsync(string typeName, string folderPath)
        {
            return await GetTypeFromFolderAsync(typeName, folderPath, null, IO.SearchOption.AllDirectories).ConfigureAwait(false);
        }
        public async Task<System.Type> GetTypeFromFolderAsync(string typeName, string folderPath, IEnumerable<string> searchPatterns, Sidvall.IO.SearchOption searchOption)
        {
            IEnumerable<string> fileNames;

            if (string.IsNullOrWhiteSpace(typeName))
                return null;
            if (searchPatterns == null)
                searchPatterns = new string[] { "*.dll", "*.exe" };
            fileNames = await this.StorageManager.GetFileNamesAsync(folderPath, searchPatterns, searchOption).ConfigureAwait(false);

            return await GetTypeAsync(typeName, fileNames).ConfigureAwait(false);
        }

        #endregion
        #region IsNullableType

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public bool IsNullableType(System.Type type)
        {
            if (type == null)
                return false;
            return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(System.Nullable<>);
        }

        #endregion
        #region SetFieldValue

        public void SetFieldValue(object item, object value, string fieldName)
        {
            if ((item == null) || (string.IsNullOrWhiteSpace(fieldName)))
                return;
            var fieldInfo = GetField(item, fieldName);
            SetFieldValue(item, value, fieldInfo);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void SetFieldValue(object item, object value, System.Reflection.FieldInfo fieldInfo)
        {
            if ((item == null) || (fieldInfo == null))
                return;
            fieldInfo.SetValue(item, value);
        }

        #endregion
        #region SetFieldValues

        public void SetFieldValues(object item, object value, System.Type fieldType)
        {
            IEnumerable<System.Reflection.FieldInfo> fieldInfos;

            if (item == null)
                return;
            if (fieldType == null)
                return;
            fieldInfos = item.GetType().GetTypeInfo().DeclaredFields;
            foreach (var fieldInfo in fieldInfos)
            {
                if (fieldInfo.FieldType == fieldType)
                    SetFieldValue(item, value, fieldInfo);
            }
        }

        #endregion
        #region SetPropertyValue

        public void SetPropertyValue(object item, object value, string propertyName)
        {
            if ((item == null) || (string.IsNullOrWhiteSpace(propertyName)))
                return;
            var propertyInfo = GetProperty(item, propertyName);
            SetPropertyValue(item, value, propertyInfo);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public void SetPropertyValue(object item, object value, System.Reflection.PropertyInfo propertyInfo)
        {
            if ((item == null) || (propertyInfo == null))
                return;
            propertyInfo.SetValue(item, value);
        }

        #endregion
        #region SetPropertyValues

        public void SetPropertyValues(object item, object value, System.Type propertyType)
        {
            IEnumerable<System.Reflection.PropertyInfo> propertyInfos;

            if (item == null)
                return;
            if (propertyType == null)
                return;
            propertyInfos = item.GetType().GetTypeInfo().DeclaredProperties;
            foreach (var propertyInfo in propertyInfos)
            {
                if (propertyInfo.PropertyType == propertyType)
                    SetPropertyValue(item, value, propertyInfo);
            }
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return "CodeManager - Sidvall.Reflection.ICode";
        }

        #endregion

        #endregion
        #region Private  Members

        #region TypeIsValid

        private static bool TypeIsValid(Sidvall.Reflection.TypeItem typeItem, Assembly assembly)
        {
            if (assembly == null)
                return true;
            if (typeItem == null)
                return false;
            if (string.IsNullOrWhiteSpace(typeItem.AssemblyName))
                return true;
            if (assembly.GetName().Name.Equals(typeItem.AssemblyName, System.StringComparison.OrdinalIgnoreCase))
                return true;
            return false;
        }

        #endregion

        #endregion
        #region Constructors

        public CodeManager(Sidvall.Reflection.ICode code)
        {
            this.Code = code;
        }

        #endregion
    }
}
